#ifndef XG_DBCONNECT_H
#define XG_DBCONNECT_H
//////////////////////////////////////////////////////////////
#include "../stdx/File.h"
#include "../stdx/Socket.h"
#include "../stdx/Reflect.h"

#define MLSQL(...) #__VA_ARGS__

enum E_DBDATA_TYPE
{
	DBData_Blob,
	DBData_Other,
	DBData_Float,
	DBData_String,
	DBData_Integer,
	DBData_DateTime
};

enum E_SQLRTN_CODE
{
	SQLRtn_Success		= 0,
	SQLRtn_NotFound		= XG_NOTFOUND,
	SQLRtn_Duplicate	= XG_DUPLICATE,
	SQLRtn_Error		= -100,
	SQLRtn_System		= -101,
	SQLRtn_ConnectLost  = -102
};

class DBConnectConfig : public Object
{
public:
	int port;
	string host;
	string user;
	string type;
	string name;
	string charset;
	string password;

	bool load(const YAMLoader* cfg)
	{
		if (cfg == NULL) cfg = YAMLoader::Instance();

		cfg->get("database.port", port);
		cfg->get("database.host", host);
		cfg->get("database.user", user);
		cfg->get("database.type", type);
		cfg->get("database.charset", charset);
		cfg->get("database.password", password);

		return cfg->get("database.name", name);
	}
};

class DBData : public Object
{
protected:
	bool none = true;

public:
	bool isNull() const
	{
		return none;
	}
	void setNullFlag(bool flag)
	{
		none = flag;
	}

	virtual void clear() = 0;
	virtual E_DBDATA_TYPE type() const = 0;
	virtual string toValueString(const string& sysname) const;
};

class DBString : public DBData
{
protected:
	string data;

public:
	DBString()
	{
	}
	DBString(const string& str)
	{
		*this = str;
	}

	void clear()
	{
		none = true;
		data.clear();
	}
	string& val()
	{
		return data;
	}
	string toString() const
	{
		return data;
	}
	E_DBDATA_TYPE type() const
	{
		return DBData_String;
	}
	DBString& operator = (const string& str)
	{
		data = str;
		none = false;
		return *this;
	}
};

class DBInteger : public DBData
{
protected:
	long long data = 0;

public:
	DBInteger()
	{
	}
	DBInteger(long long val)
	{
		*this = val;
	}
	DBInteger(const string& str)
	{
		*this = str;
	}

	void clear()
	{
		none = true;
		data = 0;
	}
	long long& val()
	{
		return data;
	}
	string toString() const
	{
		return none ? stdx::EmptyString() : stdx::str(data);
	}
	E_DBDATA_TYPE type() const
	{
		return DBData_Integer;
	}
	DBInteger& operator = (long long val)
	{
		data = val;
		none = false;
		return *this;
	}
	DBInteger& operator = (const string& str)
	{
		data = stdx::atol(str.c_str());
		none = str.empty();
		return *this;
	}
};

class DBFloat : public DBData
{
protected:
	double data = 0;

public:
	DBFloat()
	{
	}
	DBFloat(double val)
	{
		*this = val;
	}
	DBFloat(const string& str)
	{
		*this = str;
	}

	void clear()
	{
		none = true;
		data = 0;
	}
	double& val()
	{
		return data;
	}
	string toString() const
	{
		return none ? stdx::EmptyString() : stdx::str(data);
	}
	E_DBDATA_TYPE type() const
	{
		return DBData_Float;
	}
	DBFloat& operator = (double val)
	{
		data = val;
		none = false;
		return *this;
	}
	DBFloat& operator = (const string& str)
	{
		data = stdx::atof(str.c_str());
		none = str.empty();
		return *this;
	}
};

class DBDateTime : public DBData
{
protected:
	DateTime data;

public:
	DBDateTime()
	{
	}
	DBDateTime(const string& str)
	{
		*this = str;
	}
	DBDateTime(const DateTime& val)
	{
		*this = val;
	}

	void clear()
	{
		data = DateTime();
		none = true;
	}
	void update()
	{
		data.update();
		none = false;
	}
	DateTime& val()
	{
		return data;
	}
	string toString() const
	{
		return none ? stdx::EmptyString() : data.toString();
	}
	E_DBDATA_TYPE type() const
	{
		return DBData_DateTime;
	}
	DBDateTime& operator = (const string& str)
	{
		data = DateTime::FromString(str);
		none = str.empty();
		return *this;
	}
	DBDateTime& operator = (const DateTime& val)
	{
		data = val;
		none = false;
		return *this;
	}
};

class DBBlob : public DBData
{
protected:
	SmartBuffer data;

public:
	DBBlob()
	{
	}
	DBBlob(const string& str)
	{
		*this = str;
	}
	DBBlob(const SmartBuffer& val)
	{
		*this = val;
	}

	void clear()
	{
		none = true;
		data.free();
	}
	int size() const
	{
		return data.size();
	}
	SmartBuffer& val()
	{
		return data;
	}
	string toString() const
	{
		return data.str() ? data.str() : stdx::EmptyString();
	}
	E_DBDATA_TYPE type() const
	{
		return DBData_Blob;
	}
	DBBlob& operator = (const string& str)
	{
		data = str;
		none = false;
		return *this;
	}
	DBBlob& operator = (const SmartBuffer& val)
	{
		data = val;
		none = false;
		return *this;
	}
};

struct ColumnData
{
	int type;
	int size;
	int scale;
	char name[64];
	bool nullable;

	const char* getTypeString() const;
};

class RowData : public Object
{
public:
	virtual int getInt(int index);
	virtual float getFloat(int index);
	virtual double getDouble(int index);
	virtual long long getLong(int index);
	virtual DateTime getDateTime(int index);
	virtual SmartBuffer getBinary(int index);
	virtual bool getDateTime(DateTime& datetime, int index);
	virtual char* getString(int index, char* data, int len);
	
	virtual bool isNull() = 0;
	virtual string getString(int index) = 0;
	virtual int getDataLength(int index) = 0;
	virtual int getData(int index, char* data, int len) = 0;
};

class Session : public Object
{
protected:
	string name;

public:
	virtual bool clear() = 0;
	virtual bool disable() = 0;
	virtual int size() const = 0;
	virtual long getTimeout() const = 0;
	virtual bool setTimeout(long second) = 0;
	virtual bool remove(const string& key) = 0;
	virtual bool set(const map<string, string>& attrmap) = 0;
	virtual bool set(const string& key, const string& val) = 0;
	virtual bool get(const string& key, string& val) const = 0;

	bool empty() const
	{
		return size() <= 0;
	}
	bool isTimeout() const
	{
		return getTimeout() <= 0;
	}
	const string& getName() const
	{
		return name;
	}
	string get(const string& key) const
	{
		string val;

		get(key, val);

		return std::move(val);
	}
	bool setInt(const string& key, int val)
	{
		return set(key, stdx::str(val));
	}
	bool setLong(const string& key, long val)
	{
		return set(key, stdx::str(val));
	}
	bool setDouble(const string& key, double val)
	{
		return set(key, stdx::str(val));
	}
	bool getInt(const string& key, int& val) const
	{
		string tmp;

		CHECK_FALSE_RETURN(get(key, tmp));

		val = stdx::atoi(tmp.c_str());

		return true;
	}
	bool getLong(const string& key, long& val) const
	{
		string tmp;

		CHECK_FALSE_RETURN(get(key, tmp));

		val = stdx::atol(tmp.c_str());

		return true;
	}
	bool getDouble(const string& key, double& val) const
	{
		string tmp;

		CHECK_FALSE_RETURN(get(key, tmp));

		val = stdx::atof(tmp.c_str());

		return true;
	}
};

class QueryResult : public Object
{
private:
	vector<sp<Object>> vec;

public:
	virtual int rows() = 0;
	virtual int cols() = 0;
	virtual void close() = 0;
	virtual sp<RowData> next() = 0;
	virtual int getErrorCode() = 0;
	virtual string getErrorString() = 0;
	virtual string getColumnName(int index) = 0;
	virtual bool getColumnData(ColumnData& data, int index) = 0;
	
public:
	void hold(sp<Object> obj)
	{
		vec.push_back(obj);
	}
};

class DBConnect : public Object
{
public:
	class Status : public Exception
	{
	protected:
		int rowcnt;
		string sqlcmd;
		vector<string> paramlist;

	public:
		string toString() const;
		int update(DBConnect* conn, int rowcnt, const string& sqlcmd, const vector<DBData*>& paramlist);

	public:	
		int getRowCount() const
		{
			return rowcnt;
		}
		const string& getCommand() const
		{
			return sqlcmd;
		}
		const vector<string>& getParamList() const
		{
			return paramlist;
		}
		Status() : Exception(0, stdx::EmptyString())
		{
			this->rowcnt = 0;
		}
	};

protected:
	int loglevel;
	Status status;

	int exec(const char* sql);
	virtual int bind(void* stmt, const vector<DBData*>& vec);
	virtual int updateStatus(int rowcnt, const string& sqlcmd, const vector<DBData*>& paramlist = {});

public:
	DBConnect()
	{
		loglevel = eTIP;
	}
	void setLogLevel(int level)
	{
		loglevel = level;
	}
	const Status& getLastStatus() const
	{
		return status;
	}
	bool init(const DBConnectConfig& cfg)
	{
		return connect(cfg.host, cfg.port, cfg.name, cfg.user, cfg.password);
	}

	virtual string getBindString();
	virtual bool begin(bool commited = true);
	virtual int release(sp<QueryResult>& rs);
	virtual bool setCharset(const string& charset);
	virtual sp<QueryResult> quickQuery(const string& sql);
	int insert(const Object& dest, const char* tabname, const char* exclude = NULL, const char* emptynull = NULL);
	int update(const Object& dest, const char* tabname, const char* keylist, const char* exclude = NULL, const char* emptynull = NULL);
	int replace(const Object& dest, const char* tabname, const char* keylist, const char* exclude = NULL, const char* emptynull = NULL);

	virtual void close() = 0;
	virtual bool commit() = 0;
	virtual bool rollback() = 0;
	virtual int getErrorCode() = 0;
	virtual string getErrorString() = 0;
	virtual const char* getSystemName() = 0;
	virtual int execute(const string& sqlcmd) = 0;
	virtual int getTables(vector<string>& vec) = 0;
	virtual sp<QueryResult> query(const string& sqlcmd) = 0;
	virtual int execute(const string& sqlcmd, const vector<DBData*>& vec) = 0;
	virtual int getPrimaryKeys(vector<string>& vec, const string& tabname) = 0;
	virtual sp<QueryResult> query(const string& sqlcmd, const vector<DBData*>& vec) = 0;
	virtual bool connect(const string& host, int port, const string& name, const string& user, const string& password) = 0;

	static void Pack(vector<sp<DBData>>& vec, int val)
	{
		vec.push_back(newsp<DBInteger>(val));
	}
	static void Pack(vector<sp<DBData>>& vec, long val)
	{
		vec.push_back(newsp<DBInteger>(val));
	}
	static void Pack(vector<sp<DBData>>& vec, size_t val)
	{
		vec.push_back(newsp<DBInteger>(val));
	}
	static void Pack(vector<sp<DBData>>& vec, double val)
	{
		vec.push_back(newsp<DBFloat>(val));
	}
	static void Pack(vector<sp<DBData>>& vec, const char* val)
	{
		vec.push_back(val ? newsp<DBString>(val) : newsp<DBString>());
	}
	static void Pack(vector<sp<DBData>>& vec, const string& val)
	{
		vec.push_back(newsp<DBString>(val));
	}
	static void Pack(vector<sp<DBData>>& vec, const DateTime& val)
	{
		vec.push_back(newsp<DBDateTime>(val));
	}
	static void Pack(vector<sp<DBData>>& vec, const ParamVector& args)
	{
		for (int i = 0; i < args.size(); i++)
		{
			switch(args.type(i))
			{
				case eINT:
					vec.push_back(newsp<DBInteger>(args.asLong(i)));
					break;
				case eNULL:
					vec.push_back(newsp<DBString>());
					break;
				case eDOUBLE:
					vec.push_back(newsp<DBInteger>(args.asDouble(i)));
					break;
				case eBUFFER:
					vec.push_back(newsp<DBBlob>(args.asString(i)));
					break;
				default:
					vec.push_back(newsp<DBString>(args.asString(i)));
					break;
			}
		}
	}
	template<class DATA_TYPE, class ...ARGS>
	static void Pack(vector<sp<DBData>>& vec, const DATA_TYPE& val, ARGS ...args)
	{
		Pack(vec, val);
		Pack(vec, args...);
	}

	template<class DATA_TYPE, class ...ARGS>
	int execute(const string& sqlcmd, const DATA_TYPE& val, ARGS ...args)
	{
		vector<sp<DBData>> vec;

		Pack(vec, val, args...);

		vector<DBData*> tmp;

		for (auto& item : vec) tmp.push_back(item.get());

		return execute(sqlcmd, tmp);
	}
	template<class DATA_TYPE, class ...ARGS>
	sp<QueryResult> query(const string& sqlcmd, const DATA_TYPE& val, ARGS ...args)
	{
		vector<sp<DBData>> vec;

		Pack(vec, val, args...);

		vector<DBData*> tmp;

		for (auto& item : vec) tmp.push_back(item.get());
	
		sp<QueryResult> res = query(sqlcmd, tmp);

		if (res)
		{
			for (auto& item : vec) res->hold(item);
		}

		return res;
	}
	template<class DATA_TYPE, class ...ARGS>
	sp<QueryResult> find(const string& sqlcmd, const DATA_TYPE& val, ARGS ...args)
	{
		return query(sqlcmd, val, args...);
	}
	template<class ...ARGS>
	int select(int& dest, const string& sqlcmd, ARGS ...args)
	{
		sp<QueryResult> res = query(sqlcmd, args...);

		if (!res) return SQLRtn_Error;

		sp<RowData> row = res->next();

		if (!row) return 0;

		dest = row->getInt(0);

		return XG_OK;
	}
	template<class ...ARGS>
	int select(double& dest, const string& sqlcmd, ARGS ...args)
	{
		sp<QueryResult> res = query(sqlcmd, args...);

		if (!res) return SQLRtn_Error;

		sp<RowData> row = res->next();

		if (!row) return 0;

		dest = row->getDouble(0);

		return XG_OK;
	}
	template<class ...ARGS>
	int select(string& dest, const string& sqlcmd, ARGS ...args)
	{
		sp<QueryResult> res = query(sqlcmd, args...);

		if (!res) return SQLRtn_Error;

		sp<RowData> row = res->next();

		if (!row) return 0;

		dest = row->getString(0);

		return XG_OK;
	}
	template<class DATE_TYPE, class ...ARGS>
	int select(DATE_TYPE& dest, const string& sqlcmd, ARGS ...args)
	{
		sp<QueryResult> res = query(sqlcmd, args...);

		if (!res) return SQLRtn_Error;

		sp<RowData> row = res->next();

		if (!row) return 0;

		int cols = res->cols();

		if (cols <= 0) return SQLRtn_Error;
	
		vector<ReflectItem> attrs = ReflectHelper::GetAttrList<DATE_TYPE>();

		if (attrs.empty()) return XG_SYSERR;

		for (int i = 0; i < cols; i++)
		{
			const string& name = res->getColumnName(i);

			for (ReflectItem& item : attrs)
			{
				if (strcasecmp(name.c_str(), item.getName()) == 0)
				{
					item.set(&dest, row->getString(i));

					break;
				}
			}
		}

		return XG_OK;
	}
	template<class DATE_TYPE, class ...ARGS>
	int selectList(vector<DATE_TYPE>& vec, const string& sqlcmd, ARGS ...args)
	{
		vec.clear();

		sp<QueryResult> res = query(sqlcmd, args...);

		if (!res) return SQLRtn_Error;

		sp<RowData> row = res->next();

		if (!row) return vec.size();

		int cols = res->cols();

		if (cols <= 0) return SQLRtn_Error;
	
		vector<string> namelist;
		vector<ReflectItem> attrs = ReflectHelper::GetAttrList<DATE_TYPE>();

		for (int i = 0; i < cols; i++) namelist.push_back(res->getColumnName(i));

		while (row)
		{
			vec.emplace_back();

			DATE_TYPE& dest = vec.back();

			for (int i = 0; i < cols; i++)
			{
				for (ReflectItem& item : attrs)
				{
					if (strcasecmp(namelist[i].c_str(), item.getName()) == 0)
					{
						item.set(&dest, row->getString(i));

						break;
					}
				}
			}

			row = res->next();
		}

		return vec.size();
	}
};

class DBEntity : public Object
{
protected:
	string sql;
	sp<DBConnect> conn;
	sp<QueryResult> rs;

public:
	virtual bool next() = 0;
	virtual void clear() = 0;
	virtual int insert() = 0;
	virtual int remove() = 0;
	virtual sp<QueryResult> find() = 0;
	virtual string getPKCondition() = 0;
	virtual string getValue(const string& key) = 0;
	virtual int update(bool updatenull = false) = 0;
	virtual bool setValue(const string& key, const string& val) = 0;
	virtual int remove(const string& condition, const vector<DBData*>& vec = {}) = 0;
	virtual sp<QueryResult> find(const string& condition, const vector<DBData*>& vec = {}) = 0;
	virtual int update(bool updatenull, const string& condition, const vector<DBData*>& vec = {}) = 0;

public:
	DBEntity()
	{
		conn = NULL;
	}
	~DBEntity()
	{
		close();
	}

public:
	void close()
	{
		rs = NULL; conn = NULL;
	}
	bool commit()
	{
		return conn->commit();
	}
	bool rollback()
	{
		return conn->rollback();
	}
	bool begin(bool commited = true)
	{
		return conn->begin(commited);
	}
	int getErrorCode() const
	{
		return conn->getErrorCode();
	}
	string getErrorString() const
	{
		return conn->getErrorString();
	}
	sp<DBConnect> getHandle() const
	{
		return conn;
	}
	const string& getLastSQL() const
	{
		return sql;
	}
	int release(sp<QueryResult>& rs)
	{
		return conn->release(rs);
	}
	bool init(sp<DBConnect> conn, bool cleared = true)
	{
		close();

		if (cleared) clear();

		return (this->conn = conn) ? true : false;
	}
	int execute(const string& sqlcmd, const vector<DBData*>& vec = {})
	{
		this->sql = sqlcmd;

		return conn->execute(sqlcmd, vec);
	}

public:
	template<class DATA_TYPE, class ...ARGS>
	int execute(const string& sqlcmd, const DATA_TYPE& val, ARGS ...args)
	{
		vector<sp<DBData>> vec;

		DBConnect::Pack(vec, val, args...);

		vector<DBData*> tmp;

		for (auto& item : vec) tmp.push_back(item.get());

		return execute(sqlcmd, tmp);
	}
	template<class DATA_TYPE, class ...ARGS>
	int removeWhere(const string& condition, const DATA_TYPE& val, ARGS ...args)
	{
		vector<sp<DBData>> vec;

		DBConnect::Pack(vec, val, args...);

		vector<DBData*> tmp;

		for (auto& item : vec) tmp.push_back(item.get());

		return remove(condition, tmp);
	}
	template<class DATA_TYPE, class ...ARGS>
	sp<QueryResult> findWhere(const string& condition, const DATA_TYPE& val, ARGS ...args)
	{
		vector<sp<DBData>> vec;

		DBConnect::Pack(vec, val, args...);

		vector<DBData*> tmp;

		for (auto& item : vec) tmp.push_back(item.get());
	
		sp<QueryResult> res = find(condition, tmp);

		if (res)
		{
			for (auto& item : vec) res->hold(item);
		}

		return res;
	}
	template<class DATA_TYPE, class ...ARGS>
	int updateWhere(bool updatenull, const string& condition, const DATA_TYPE& val, ARGS ...args)
	{
		vector<sp<DBData>> vec;

		DBConnect::Pack(vec, val, args...);

		vector<DBData*> tmp;

		for (auto& item : vec) tmp.push_back(item.get());

		return update(updatenull, condition, tmp);
	}
};
//////////////////////////////////////////////////////////////
#endif